package ru.spbau.bytecode.graph.io;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import ru.spbau.bytecode.graph.CompositeVertex;
import ru.spbau.bytecode.graph.Edge;
import ru.spbau.bytecode.graph.Graph;
import ru.spbau.bytecode.graph.GraphBuilder;
import ru.spbau.bytecode.graph.GraphException;
import ru.spbau.bytecode.graph.GraphSaver;
import ru.spbau.bytecode.graph.Vertex;


public class XMLGraphIO implements GraphBuilder, GraphSaver {
	
	private static final String GRAPH = "Graph";
	private static final String VERTEX = "Vertex";
	private static final String COMPOSITE_VERTEX = "CompositeVertex";
	private static final String EDGE = "Edge";
	private static final String SOURCE_VERTEX_NAME = "sourceVertexName";
	private static final String TARGET_VERTEX_NAME = "targetVertexName";
	private static final String NAME = "name";
	private static final String VISIBLE = "visible";
	private static final String LOCATION = "location";
	private static final String SIZE = "size";
	private static final String BOUNDS = "bounds";
	
	
	@Override
	public Graph buildGraph(File file) throws GraphException {
		Graph g = new Graph();
		DefaultHandler handler = new XMLHandler(g);
		try {
			SAXParserFactory.newInstance().newSAXParser().parse(file, handler);
		} catch (SAXException e) {
			throw new GraphException("Can't create parser", e);
		} catch (IOException e) {
			throw new GraphException("Can't create parser", e);
		} catch (ParserConfigurationException e) {
			throw new GraphException("Can't create parser", e);
		}
		return g;
	}
	
	private String getOpenTag(String s) {
		return "<" + s + ">";
	}
	
	private String getCloseTag(String s) {
		return "</" + s + ">";
	}
	
	private String getUnpairedTag(String s) {
		return "<" + s + "/>";
	}

	@Override
	public void saveGraph(Graph graph, File file) throws GraphException {
		System.err.println(file.getAbsolutePath());
		PrintWriter writer;
		try {
			writer = new PrintWriter(file);
		} catch (FileNotFoundException e) {
			throw new GraphException("Can't create file", e);
		}
		
		writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
		
		writer.println(getOpenTag(GRAPH));
		for (CompositeVertex v: graph.getVertices()) {
			writer.println(getOpenTag(COMPOSITE_VERTEX));
			writeCommonVertexInfo(v, writer);
			
			for (Vertex u: v.getInnerVertices()) {
				writer.println(getOpenTag(VERTEX));
				writeCommonVertexInfo(u, writer);
				writer.println(getCloseTag(VERTEX));
			}
			
			writer.println(getCloseTag(COMPOSITE_VERTEX));
		}
		
		for (Edge e: graph.getEdges()) {
			writer.println(getOpenTag(EDGE));
			writer.println(getOpenTag(SOURCE_VERTEX_NAME) +
					e.getSourceVertex().getName() + getCloseTag(SOURCE_VERTEX_NAME));
			writer.println(getOpenTag(TARGET_VERTEX_NAME) +
					e.getTargetVertex().getName() + getCloseTag(TARGET_VERTEX_NAME));
			writer.println(getCloseTag(EDGE));
		}
		writer.println(getCloseTag(GRAPH));
		
		writer.close();
	}
	
	private void writeCommonVertexInfo(Vertex v, PrintWriter writer) {
		writer.println(getOpenTag(NAME) + v.getName() + getCloseTag(NAME));
		writer.println(getOpenTag(VISIBLE) + v.isVisible() + getCloseTag(VISIBLE));
		
		String locationX = " x=\"" + v.getLocation().x + "\"";
		String locationY = " y=\"" + v.getLocation().y + "\"";
		writer.println(getUnpairedTag(LOCATION + locationX + locationY));
		
		String sizeWidth = " width=\"" + v.getSize().width + "\"";
		String sizeHeight = " height=\"" + v.getSize().height + "\"";
		writer.println(getUnpairedTag(SIZE + sizeWidth + sizeHeight));
		
		String boundsX = " x=\"" + v.getBounds().x + "\"";
		String boundsY = " y=\"" + v.getBounds().y + "\"";
		String boundsWidth = " width=\"" + v.getBounds().width + "\"";
		String boundsHeight = " height=\"" + v.getBounds().height + "\"";
		writer.println(getUnpairedTag(
				BOUNDS + boundsX + boundsY + boundsWidth + boundsHeight));
	}
	
	private class XMLHandler extends DefaultHandler {
		
		private final Graph graph;
		
		private CompositeVertex compositeVertex;
		private Vertex vertex;
		private Vertex sourceVertex;
		private Vertex targetVertex;
		
		private boolean inCompositeVertex = false;
		private boolean inVertex = false;
		private boolean inName = false;
		private boolean inVisible = false;
		private boolean inEdge = false;
		private boolean inSourceVertexName = false;
		private boolean inTargetVertexName = false;
		
		
		public XMLHandler(Graph graph) {
			this.graph = graph;
		}
		
		@Override
		public void startElement(String uri, String localName, String qName,
				Attributes attributes) throws SAXException {
			
			
				
			if (GRAPH.equalsIgnoreCase(qName)) {
				
			} else if (COMPOSITE_VERTEX.equalsIgnoreCase(qName)) {
				inCompositeVertex = true;
			} else if (VERTEX.equalsIgnoreCase(qName)) {
				inVertex = true;
			} else if (LOCATION.equalsIgnoreCase(qName)) {
				int x = Integer.parseInt(attributes.getValue(0));
				int y = Integer.parseInt(attributes.getValue(1));
				Point location = new Point(x, y);
				if (inVertex) {
					vertex.setLocation(location);
				} else if (inCompositeVertex) {
					compositeVertex.setLocation(location);
				} else {
					throw new SAXException(
							"tag <location> is not in <Vertex> or <CompositeVertex> tag");
				}
			} else if (SIZE.equalsIgnoreCase(qName)) {
				int width = Integer.parseInt(attributes.getValue(0));
				int height = Integer.parseInt(attributes.getValue(1));
				Dimension size = new Dimension(width, height);
				if (inVertex) {
					vertex.setSize(size);
				} else if (inCompositeVertex) {
					compositeVertex.setSize(size);
				} else {
					throw new SAXException(
							"tag <size> is not in <Vertex> or <CompositeVertex> tag");
				}
			} else if (BOUNDS.equalsIgnoreCase(qName)) {
				int x = Integer.parseInt(attributes.getValue(0));
				int y = Integer.parseInt(attributes.getValue(1));
				int width = Integer.parseInt(attributes.getValue(2));
				int height = Integer.parseInt(attributes.getValue(3));
				Rectangle bounds = new Rectangle(x, y, width, height);
				if (inVertex) {
					vertex.setBounds(bounds);
				} else if (inCompositeVertex) {
					compositeVertex.setBounds(bounds);
				} else {
					throw new SAXException(
							"tag <bounds> is not in <Vertex> or <CompositeVertex> tag");
				}
			} else if (NAME.equalsIgnoreCase(qName)) {
				inName = true;
			} else if (VISIBLE.equalsIgnoreCase(qName)) {
				inVisible = true;
			} else if (EDGE.equalsIgnoreCase(qName)) {
				inEdge = true;
			} else if (SOURCE_VERTEX_NAME.equalsIgnoreCase(qName)) {
				inSourceVertexName = true;
			} else if (TARGET_VERTEX_NAME.equalsIgnoreCase(qName)) {
				inTargetVertexName = true;
			} else {
				throw new SAXException("Unknown tag: " + qName);
			}
		}
		
		@Override
		public void endElement(String uri, String localName,
				String qName) throws SAXException {
			
			if (GRAPH.equalsIgnoreCase(qName)) {
				
			} else if (COMPOSITE_VERTEX.equalsIgnoreCase(qName)) {
				graph.addVertex(compositeVertex);
				inCompositeVertex = false;
			} else if (VERTEX.equalsIgnoreCase(qName)) {
				compositeVertex.addInnerVertex(vertex);
				inVertex = false;
			} else if (LOCATION.equalsIgnoreCase(qName)) {
				
			} else if (SIZE.equalsIgnoreCase(qName)) {
				
			} else if (BOUNDS.equalsIgnoreCase(qName)) {
				
			} else if (NAME.equalsIgnoreCase(qName)) {
				inName = false;
			} else if (VISIBLE.equalsIgnoreCase(qName)) {
				inVisible = false;
			} else if (EDGE.equalsIgnoreCase(qName)) {
				graph.addEdge(new Edge(sourceVertex, targetVertex));
				inEdge = false;
			} else if (SOURCE_VERTEX_NAME.equalsIgnoreCase(qName)) {
				inSourceVertexName = false;
			} else if (TARGET_VERTEX_NAME.equalsIgnoreCase(qName)) {
				inTargetVertexName = false;
			} else {
				throw new SAXException("Unknown tag: " + qName);
			}
		}
		
		@Override
		public void characters(char ch[], int start, int length) throws SAXException {
			String input = new String(ch, start, length);
			
			if (inVisible) {
				boolean selected;
				if (input.equals("true")) {
					selected = true;
				} else if (input.equals("false")) {
					selected = false;
				} else {
					throw new SAXException("Invalid information in <selected> tag");
				}
				
				if (inVertex) {
					vertex.setVisible(selected);
				} else if (inCompositeVertex) {
					compositeVertex.setVisible(selected);
				} else {
					throw new SAXException(
							"Tag <selected> is not in <Vertex> or <CompositeVertex> tag");
				}
			} else if (inName) {
				if (inVertex) {
					vertex = new Vertex(input);
				} else if (inCompositeVertex) {
					compositeVertex = new CompositeVertex(input);
				} else {
					throw new SAXException(
							"Tag <name> is not in <Vertex> or <CompositeVertex> tag");
				}
			} else if (inSourceVertexName) {
				if (inEdge) {
					sourceVertex = new Vertex(input);
				} else {
					throw new SAXException(
							"Tag <sourceVertexName> is not in <Edge> tag");
				}
			} else if (inTargetVertexName) {
				if (inEdge) {
					targetVertex = new Vertex(input);
				} else {
					throw new SAXException(
							"Tag <targetVertexName> is not in <Edge> tag");
				}
			}
		}
	}

	@Override
	public String getName() {
		return "XMLGraphIO";
	}

	@Override
	public String getExtension() {
		return ".xml";
	}

}
